using System;
using System.Collections.Generic;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Samplers
{
    public class SampleBlock : IEquatable<SampleBlock>
    {
        int d = 0;

        public int SamplerPerBlock
        {
            get { return BlockWidth * BlockHeight * SamplesPerIteration; }
        }

        public readonly long ID;
        public int Xstart, Ystart, BlockWidth, BlockHeight;
        public int SamplesPerIteration, TotalSamples, BlockPass;
        public bool Important;
        public float Importance;


        public float CachedError;


        internal SampleBlock()
        {
            ID = DateTime.UtcNow.Ticks;
            Importance = 1.0f;
        }

        public int SamplesInBlock(int nx = 1)
        {
            return (int)Math.Round(nx*Importance);
        }

        public SampleBlock(int xs, int ys, int w, int h)
            : this()
        {
            Xstart = xs;
            Ystart = ys;
            BlockWidth = w;
            BlockHeight = h;
            cx = xs;
            cy = ys;
        }

        private int currentSample = 0, cx, cy;

        public Sample GetSample(ISampler sampler)
        {
            var sample = new Sample(sampler);
            var rx = sample.GetLazyValue();
            var ry = sample.GetLazyValue();
            sample.imageX = (Xstart - d) + rx * (BlockWidth +  d);
            sample.imageY = (Ystart - d) + ry * (BlockHeight + d);

            if (sample.imageX >= ((sampler as AdaptiveBlockSampler).screenWidth))
            {
                sample.imageX = ((sampler as AdaptiveBlockSampler).screenWidth);
            }

            if (sample.imageX > (Xstart + BlockWidth))
            {
                sample.imageX = Xstart + BlockWidth-1;
            }

            if (sample.imageX < (Xstart))
            {
                sample.imageX = Xstart;
            }

            if (sample.imageY > ((sampler as AdaptiveBlockSampler).screenHeight))
            {
                sample.imageY = ((sampler as AdaptiveBlockSampler).screenHeight);
            }

            if (sample.imageY > (Ystart + BlockHeight))
            {
                sample.imageY = Ystart + BlockHeight;
            }

            if (sample.imageY < (Ystart))
            {
                sample.imageY = Ystart;
            }

            sample.pass = BlockPass;
            TotalSamples++;
            sampler.TotalSamples++;
            return sample;
        }

        public float BlockError(AdaptiveImageFilm film)
        {
            float result = 0f;
            int i, j, pixelsInBlock = 0;
            for (j = Math.Max(0, Ystart); j < Math.Min(Ystart + BlockHeight, film.Height - 1); j++)
            {
                for (i = Math.Max(0, Xstart); i < Math.Max(Xstart + BlockWidth, film.Width - 1); i++)
                {
                    var ep = film.Variance(i, j);
                    result += ep;
                    pixelsInBlock++;
                }
            }
            var r = Math.Sqrt((BlockWidth * BlockHeight) / (double)(film.Width * film.Height));
            //var N = film.Width * film.Height * SamplesPerIteration;
            CachedError = (float)(r / pixelsInBlock) * result;
            return CachedError;
        }

        public ICollection<SampleBlock> MassSplit(int x, int sp)
        {

            var currentBlock = this;

            var res = new List<SampleBlock>() { currentBlock };

            int iter = 0;

            while (iter < x)
            {
                var tmp = new List<SampleBlock>();
                for (int index  = 0; index < res.Count; index++)
                {
                    var sampleBlock = res[index];
                    tmp.AddRange(sampleBlock.Split(sp));
                    res.Remove(sampleBlock);
                }
                res.AddRange(tmp);
                iter++;
            }

            return res;
        }

        public SampleBlock[] Split(int samplesPerIteration)
        {
            var blockToSplit = this;
            if (((blockToSplit.BlockWidth) <= 4) || (blockToSplit.BlockHeight) <= 3)
            {
                return new SampleBlock[0];
                //new[] { blockToSplit };
            }


            var blockWidth = blockToSplit.BlockWidth / 2;
            var blockHeight = blockToSplit.BlockHeight / 2;
            var lt = new SampleBlock
                {
                    BlockWidth = blockWidth,
                    BlockHeight = blockHeight,
                    Xstart = blockToSplit.Xstart,
                    Ystart = blockToSplit.Ystart,
                    SamplesPerIteration = samplesPerIteration
                };
            var rt = new SampleBlock
                {
                    BlockWidth = blockWidth,
                    BlockHeight = blockHeight,
                    Xstart = blockToSplit.Xstart + (blockWidth) + d,
                    Ystart = blockToSplit.Ystart,
                    SamplesPerIteration = samplesPerIteration
                };

            var lb = new SampleBlock
                {
                    BlockWidth = blockWidth,
                    BlockHeight = blockHeight,
                    Xstart = blockToSplit.Xstart,
                    Ystart = blockToSplit.Ystart + (blockHeight) + d,
                    SamplesPerIteration = samplesPerIteration
                };
            var rb = new SampleBlock
                {
                    BlockWidth = blockWidth,
                    BlockHeight = blockHeight,
                    Xstart = blockToSplit.Xstart + blockWidth + d,
                    Ystart = blockToSplit.Ystart + blockHeight + d,
                    SamplesPerIteration = samplesPerIteration
                };
            return new[]
                {
                    lt, rt, lb, rb
                };
        }

        public bool Equals(SampleBlock other)
        {
            return
                this.Xstart.Equals(other.Xstart) &
                this.Ystart.Equals(other.Ystart) &
                this.BlockWidth.Equals(other.BlockWidth) &
                this.BlockHeight.Equals(other.BlockHeight);
        }

        public override string ToString()
        {
            return String.Format("[XS:{0},YS:{1},W:{2},H:{3}]", Xstart, Ystart, BlockWidth, BlockHeight);
        }

        public override bool Equals(object obj)
        {
            if (obj is SampleBlock)
            {
                return this.Equals((SampleBlock)obj);
            }

            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return ID.GetHashCode();
        }


    }
}